package cn.com.eado.crm.model

import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import cn.com.eado.crm.app.utils.LogUtil
import cn.com.eado.crm.m.AppExecutors
import cn.com.eado.crm.model.net.ApiEmptyResponse
import cn.com.eado.crm.model.net.ApiErrorResponse
import cn.com.eado.crm.model.net.ApiResponse
import cn.com.eado.crm.model.net.ApiSuccessResponse

/**
 * A generic class that can provide a resource backed by both the sqlite database and the network.
 *
 *
 * You can read more about it in the [Architecture
 * Guide](https://developer.android.com/arch).
 * @param <ResultType>
 * @param <RequestType>
</RequestType></ResultType> */
abstract class NetworkBoundResource<ResultType, RequestType>
@MainThread constructor(private val appExecutors: AppExecutors) {

    private val result = MediatorLiveData<Resource<ResultType>>()
//    private var job: Job? = null

    init {
        result.value = Resource.loading(null)
        @Suppress("LeakingThis")
        if (shouldLoadFromDb()){
            val dbSource = loadFromDb()
            result.addSource(dbSource) { data ->
                result.removeSource(dbSource)
                if (shouldFetch(data)) {
                    fetchFromNetwork(dbSource)
                } else {
                    result.addSource(dbSource) { newData ->
                        setValue(Resource.success(newData))
                    }
                }
            }
        }else{
            if (shouldFetch(null)) {
                fetchFromNetwork2()
            }
        }
    }

    @MainThread
    private fun setValue(newValue: Resource<ResultType>) {
        if (result.value != newValue) {
            result.value = newValue
        }
    }

    private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
        val apiResponse = createCall()  //发起http请求

        // we re-attach dbSource as a new source, it will dispatch its latest value quickly
        result.addSource(dbSource) { newData ->
            setValue(Resource.loading(newData))
        }

        result.addSource(apiResponse) { response ->
            result.removeSource(apiResponse)
            result.removeSource(dbSource)
            when (response) {
                is ApiSuccessResponse -> {
                    appExecutors.diskIO().execute {
                        saveCallResult(processResponse(response))
                        appExecutors.mainThread().execute {
                            // we specially request a new live data,
                            // otherwise we will get immediately last cached value,
                            // which may not be updated with latest results received from network.
                            result.addSource(loadFromDb()) { newData ->
                                setValue(Resource.success(newData))
                            }
                        }
                    }
                    //用协程代替线程池
//                    job = GlobalScope.launch(Dispatchers.IO) {
//                        saveCallResult(processResponse(response))
//                        launch(Dispatchers.Main) {
//                            result.addSource(loadFromDb()) { newData ->
//                                if (newData == null) LogUtil.e("tag", "newData")
//                                setValue(Resource.success(newData))
//                            }
//                        }
//                    }
                }
                is ApiEmptyResponse -> {
                    appExecutors.mainThread().execute {
                        // reload from disk whatever we had
                        result.addSource(loadFromDb()) { newData ->
                            setValue(Resource.success(newData))
                        }
                    }
//                    LogUtil.e("tag", "ApiEmptyResponse")
//                    //用协程代替线程池
//                    job = GlobalScope.launch(Dispatchers.Main) {
//                        result.addSource(loadFromDb()) { newData ->
//                            setValue(Resource.success(newData))
//                        }
//                    }
                }
                is ApiErrorResponse -> {
                    onFetchFailed()
                    result.addSource(dbSource) { newData ->
//                        setValue(Resource.error(response.errorMessage, newData))
                        val msg = if (response.errorMessage.contains("No address associated with hostname")) "请检查网络" else response.errorMessage
                        setValue(Resource.error(msg, newData))
                    }
                }
            }
        }
    }

    private fun fetchFromNetwork2() {
        val apiResponse = createCall2()  //发起http请求
        result.addSource(apiResponse!!) { response ->
            result.removeSource(apiResponse)
            when (response) {
                is ApiSuccessResponse -> result.value = Resource.success(response.body)
                is ApiEmptyResponse -> result.value = Resource.success(null)
//                is ApiErrorResponse -> result.value = Resource.error(response.errorMessage, null)
                is ApiErrorResponse -> {
                    val msg = if (response.errorMessage.contains("No address associated with hostname")) "请检查网络或服务器地址" else response.errorMessage
                    result.value = Resource.error(msg, null)
                }
            }
        }
    }

    protected open fun onFetchFailed() {
    }

    fun asLiveData() = result as LiveData<Resource<ResultType>>

    @WorkerThread
    protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body

    @WorkerThread
    protected abstract fun saveCallResult(item: RequestType)

    @MainThread
    protected abstract fun shouldFetch(data: ResultType?): Boolean

    @MainThread
    protected abstract fun loadFromDb(): LiveData<ResultType>

    @MainThread
    protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>

    @MainThread
    protected abstract fun shouldLoadFromDb(): Boolean  //增加方法：是否加载数据库？如果为false，直接从网络获取

    @MainThread
    protected abstract fun createCall2(): LiveData<ApiResponse<ResultType>>?  //直接从网络获取
}